package com.yj.ccs.abs.boot.pagination;

import com.yj.ccs.abs.util.encryption.Reflections;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;

/**
 * Copyright 2017 (C) Yunjian-VC
 * Created on : 2017/12/8
 * Author     : Unbelievable9
 **/
@Intercepts({@Signature(method = "prepare", type = StatementHandler.class, args = {Connection.class, Integer.class})})
@SuppressWarnings("unchecked")
public class PageInterceptor implements Interceptor {

    /**
     * 根据PageDto中的参数，生成分页所需SQL
     *
     * @param pageDto 分页对象
     * @param sql     查询SQL
     * @return 带分页查询的SQL
     */
    private String retrievePageSQL(PageDto<?> pageDto, String sql) {
        StringBuffer stringBuffer = new StringBuffer(sql);

        Integer offset = (pageDto.getPageNumber() - 1) * pageDto.getPageSize();
        stringBuffer.append(" LIMIT ").append(offset).append(",").append(pageDto.getPageSize());

        return stringBuffer.toString();
    }

    /**
     * 生成查询结果数量SQL
     * @param sql 原始SQL
     * @return 查询结果数量SQL
     */
    private String generateCountSQL(String sql) {
        return "SELECT COUNT(1) FROM" + "(" + sql + ") abs_t";
    }

    private PageDto<?> getPageDtoFromObject(Object object) {
        Object targetObject = null;
        if (object instanceof HashMap) {
            HashMap<Object, Object> parameterMap = (HashMap<Object, Object>) object;

            if (parameterMap.containsKey("page_dto")) {
                targetObject = parameterMap.get("page_dto");
            }

            if (targetObject != null && targetObject instanceof PageDto<?>) {
                 targetObject = object;
            }
        } else if (object instanceof PageDto<?>) {
            targetObject = object;
        }

        return (PageDto<?>) targetObject;
    }

    /**
     * 根据截取到的SQL信息，设置最终分页查询结果的总数
     * @param object 查询对象
     * @param mappedStatement SQL Map 对象
     * @param connection SQL 连接对象
     */
    private void setPageTotalItem(Object object, MappedStatement mappedStatement, Connection connection) {
        BoundSql boundSql = mappedStatement.getBoundSql(object);

        PageDto<?> pageDto = getPageDtoFromObject(object);

        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), generateCountSQL(boundSql.getSql()), parameterMappings, pageDto);

        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        try {
            ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, pageDto, countBoundSql);
            preparedStatement = connection.prepareStatement(countBoundSql.getSql());
            parameterHandler.setParameters(preparedStatement);

            resultSet = preparedStatement.executeQuery();

            if (resultSet.next()) {
                Integer total = resultSet.getInt(1);
                pageDto.updateTotalItem(total);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                if (preparedStatement != null) {
                    preparedStatement.close();
                }

                if (resultSet != null) {
                    resultSet.close();
                }

            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        RoutingStatementHandler routingStatementHandler = (RoutingStatementHandler) invocation.getTarget();
        StatementHandler statementHandler = (StatementHandler) Reflections.getFieldValue(routingStatementHandler, "delegate");
        BoundSql boundSql = statementHandler.getBoundSql();

        Object object = boundSql.getParameterObject();
        PageDto<?> pageDto = getPageDtoFromObject(object);

        if (pageDto != null) {
            MappedStatement mappedStatement = (MappedStatement) Reflections.getFieldValue(statementHandler, "mappedStatement");
            Connection connection = (Connection) invocation.getArgs()[0];

            // 设置总数
            this.setPageTotalItem(object, mappedStatement, connection);

            // 生成分页SQL
            String pageSql = this.retrievePageSQL(pageDto, boundSql.getSql());
            Reflections.setFieldValue(boundSql, "sql", pageSql);

            // 放入ThreadLocal中
            PageThread.setPageDto(pageDto);
        }

        return invocation.proceed();
    }

    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
}
